{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 题目\n",
    "设计一个控制台程序, 模拟商场收银软件，根据客户购买商品的单价和数量，计算总价。\n",
    "\n",
    "## 基础版本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输入商品单价:40\n",
      "输入商品数量:9\n",
      "当前总价: 360.00\n"
     ]
    }
   ],
   "source": [
    "price = float(input(\"输入商品单价:\"))\n",
    "number = int(input(\"输入商品数量:\"))\n",
    "total = (price * number)\n",
    "print(\"当前总价: %.2f\" % total)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "上述程序仅仅实现了基本功能，但是当商场有打折活动，例如八折，五折等，就不满足需求了，折扣的方法还可能有满减活动，例如满300减100，满500减200等。假设只有打折和满减两种促销活动，那么这就很像上一章节的计算器，支持正常收费，打折活动和满减活动三种计算方法，可以用简单工厂方法实现。\n",
    "\n",
    "## 改进版本1.0——简单工厂模式"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "class CashBase():\n",
    "    \"\"\"\n",
    "    基础类\n",
    "    \"\"\"\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    def __init__(self):\n",
    "        self.final_price = None\n",
    "        \n",
    "    @abstractmethod\n",
    "    def accept_cash(self):\n",
    "        pass\n",
    "\n",
    "class CashNormal(CashBase):\n",
    "    \"\"\"\n",
    "    正常收费\n",
    "    \"\"\"\n",
    "    \n",
    "    def accept_cash(self, money):\n",
    "        self.final_price = money\n",
    "        return self.final_price\n",
    "\n",
    "class CashRebate(CashBase):\n",
    "    \"\"\"\n",
    "    打折活动\n",
    "    \"\"\"\n",
    "    def __init__(self, rebate):\n",
    "        self.rebate = rebate\n",
    "    \n",
    "    def accept_cash(self, money):\n",
    "        self.final_price = money * self.rebate\n",
    "        return self.final_price\n",
    "\n",
    "class CashReturn(CashBase):\n",
    "    \"\"\"\n",
    "    满减活动\n",
    "    \"\"\"\n",
    "    def __init__(self, return_condition, return_money):\n",
    "        self.return_condition = return_condition\n",
    "        self.return_money = return_money\n",
    "        \n",
    "    def accept_cash(self, money):\n",
    "        if money >= self.return_condition:\n",
    "            self.final_price = money - self.return_money\n",
    "        else:\n",
    "            self.final_price = money\n",
    "        return self.final_price\n",
    "\n",
    "class CashFactory():\n",
    "    \"\"\"\n",
    "    收费方式工厂类\n",
    "    \"\"\"\n",
    "    # 类的变量，类似静态变量，通过`类名.变量名`访问\n",
    "    cash_accepter_map = {\n",
    "            \"正常收费\": CashNormal(),\n",
    "            \"满300减100\": CashReturn(300, 100),\n",
    "            \"打8折\": CashRebate(0.8)\n",
    "        }\n",
    "    \n",
    "    @staticmethod\n",
    "    def createCashAccepter(cash_type):\n",
    "        if cash_type in CashFactory.cash_accepter_map:\n",
    "            return CashFactory.cash_accepter_map[cash_type]\n",
    "        else:\n",
    "            return None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 客户端代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输入商品单价:10\n",
      "输入商品数量:50\n",
      "1:正常收费\n",
      "2:满300减100\n",
      "3:打8折\n",
      "选择收费方式(1~3)3\n",
      "应收: 500.00\n",
      "实收: 400.00\n"
     ]
    }
   ],
   "source": [
    "price = float(input(\"输入商品单价:\"))\n",
    "number = int(input(\"输入商品数量:\"))\n",
    "cash_type_list = [\"正常收费\", \"满300减100\", \"打8折\"]\n",
    "for i in cash_type_list:\n",
    "    print(\"{}:{}\".format(cash_type_list.index(i)+1, i))\n",
    "cash_type_index = int(input(\"选择收费方式(1~3)\"))\n",
    "\n",
    "total = price * number\n",
    "cash_accepter = CashFactory.createCashAccepter(cash_type_list[cash_type_index-1])\n",
    "print(\"应收: %.2f\" % total)\n",
    "total = cash_accepter.accept_cash(total)\n",
    "print(\"实收: %.2f\" % total)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "1. 如果同时支持打折和满减，需要如何处理？\n",
    "2. 简单工厂模式主要解决对象的创建问题，无法解决对象经常改动的问题，例如折扣和满减力度是经常变化的，不能每次改动都改代码;\n",
    "3. 算法经常改动, 需要用到策略模式；\n",
    "4. 封装变化点是面向对象的一种重要的思维方式。\n",
    "\n",
    "## 策略模式\n",
    "\n",
    "该模式定义了算法家族，分别封装起来，让它们之间可以互相替换，此模式让算法的变化，不会影响到使用算法的客户。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "class CashBase():\n",
    "    \"\"\"\n",
    "    抽象策略:基础类\n",
    "    \"\"\"\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    def __init__(self):\n",
    "        self.final_price = None\n",
    "        \n",
    "    @abstractmethod\n",
    "    def accept_cash(self):\n",
    "        pass\n",
    "\n",
    "class CashNormal(CashBase):\n",
    "    \"\"\"\n",
    "    具体策略:正常收费\n",
    "    \"\"\"\n",
    "    \n",
    "    def accept_cash(self, money):\n",
    "        self.final_price = money\n",
    "        return self.final_price\n",
    "\n",
    "class CashRebate(CashBase):\n",
    "    \"\"\"\n",
    "    具体策略:打折活动\n",
    "    \"\"\"\n",
    "    def __init__(self, rebate):\n",
    "        self.rebate = rebate\n",
    "    \n",
    "    def accept_cash(self, money):\n",
    "        self.final_price = money * self.rebate\n",
    "        return self.final_price\n",
    "\n",
    "class CashReturn(CashBase):\n",
    "    \"\"\"\n",
    "    具体策略:满减活动\n",
    "    \"\"\"\n",
    "    def __init__(self, return_condition, return_money):\n",
    "        self.return_condition = return_condition\n",
    "        self.return_money = return_money\n",
    "        \n",
    "    def accept_cash(self, money):\n",
    "        if money >= self.return_condition:\n",
    "            self.final_price = money - self.return_money\n",
    "        else:\n",
    "            self.final_price = money\n",
    "        return self.final_price\n",
    "\n",
    "class CashContext():\n",
    "    \"\"\"\n",
    "    策略上下文类(基础版本)，用具体策略类来配置，维护一个具体策略对象的引用\n",
    "    \"\"\"\n",
    "    def __init__(self, cash_strategy):\n",
    "        self.cash_strategy = cash_strategy\n",
    "    \n",
    "    def get_result(slef, money):\n",
    "        return self.cash_strategy.accept_cash(money)        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "在CashContext类中，我们需要传入一个具体策略类来进行配置，在商场收银软件这个场景中，那就是不同的收费策略，那么如何生成不同的收费策略对象呢？可以将策略模式和简单工厂相结合。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CashContext():\n",
    "    \"\"\"\n",
    "    策略上下文类(改进版本)，用具体策略类来配置，维护一个具体策略对象的引用\n",
    "    \"\"\"\n",
    "    # 类的变量，类似静态变量，通过`类名.变量名`访问\n",
    "    cash_accepter_map = {\n",
    "            \"正常收费\": CashNormal(),\n",
    "            \"满300减100\": CashReturn(300, 100),\n",
    "            \"打8折\": CashRebate(0.8)\n",
    "        }\n",
    "    def __init__(self, cash_type):\n",
    "        self.cash_strategy = CashContext.cash_accepter_map[cash_type]\n",
    "    \n",
    "    def get_result(self, money):\n",
    "        return self.cash_strategy.accept_cash(money)   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 客户端代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输入商品单价:10\n",
      "输入商品数量:10\n",
      "1:正常收费\n",
      "2:满300减100\n",
      "3:打8折\n",
      "选择收费方式(1~3)3\n",
      "应收: 100.00\n",
      "实收: 80.00\n"
     ]
    }
   ],
   "source": [
    "price = float(input(\"输入商品单价:\"))\n",
    "number = int(input(\"输入商品数量:\"))\n",
    "cash_type_list = [\"正常收费\", \"满300减100\", \"打8折\"]\n",
    "for i in cash_type_list:\n",
    "    print(\"{}:{}\".format(cash_type_list.index(i)+1, i))\n",
    "cash_type_index = int(input(\"选择收费方式(1~3)\"))\n",
    "\n",
    "total = price * number\n",
    "cash_context = CashContext(cash_type_list[cash_type_index-1])\n",
    "print(\"应收: %.2f\" % total)\n",
    "total = cash_context.get_result(total)\n",
    "print(\"实收: %.2f\" % total)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "策略模式+简单工厂和仅用简单工厂模式的区别在哪里呢？\n",
    "\n",
    "    简单工厂\n",
    "    cash_accepter = CashFactory.createCashAccepter(cash_type_list[cash_type_index-1])\n",
    "    ...\n",
    "    total = cash_accepter.accept_cash(total)\n",
    "    \n",
    "    策略模式+简单工厂\n",
    "    cash_context = CashContext(cash_type_list[cash_type_index-1])\n",
    "    ...\n",
    "    total = cash_context.get_result(total)\n",
    "    \n",
    "- 简单工厂需要让客户端认识两个类，`CashFactory`和`CashBase` \n",
    "- 策略模式+简单工厂，客户端只需要认识一个类，`CashContext`\n",
    "- 客户端实例化的是`CashContext`的对象，调用的是`CashContext`的`get_result`方法，这使得具体的收费策略彻底与客户端分离，甚至连策略的基类`CashBase`都不需要客户端认识。\n",
    "\n",
    "### 策略模式解析\n",
    "1. 策略模式是一种定义一系列算法的方法，从概念上来看，所有这些算法完成的都是相同的工作，只是实现不同，它可以以相同的方式调用素有的算法，减少了各种算法类与使用算法类之间的耦合[DPE]。\n",
    "2. 策略模式的Strategy层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能[DP]，例如计算费用的结果get_result。\n",
    "3. 策略模式可以简化单元测试，因为每个算法都有自己的类，可以用过自己的接口单独测试[DPE]。\n",
    "4. 策略模式是用来封装算法的，但是实践中，可以用它来封装几乎任何类型的规则，只要需要不同时间应用不同业务规则，就可以考虑使用策略模式处理这种变化的可能性[DPE]。\n",
    "\n",
    "### 美中不足\n",
    "在CashContext中用到了一个dict()型的类的变量`cash_accepter_map`保存各种算法策略，如果新增`满200减50`的策略，那么还要更新`cash_accepter_map`，这显得并不优雅，`任何需要的变更都需要成本，但是成本的高低是有差异的`，为了更加优雅，降低变更成本，可以使用`反射技术`，这一技术将在`抽象工厂模式`中介绍。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
